package drazing;

import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferStrategy;
import javax.swing.JFrame;


/**
 * Clase principal de la aplicación
 *
 * @author Jorge Belenguer Beltrán
 * @version 1.01
 */
public final class DrazingApp extends JFrame implements Runnable
{
    //Version
    private static final String version = "1.01";

    //Parametros de rendimiento
    private static final int NUM_BUFFERS = 3;
    private static int DEFAULT_FPS = 200;
    private static final int NO_DELAYS_PER_YIELD = 1;          // Frames with a delay of 0 ms before the animation thread yields to other running threads.
    private static int MAX_FRAME_SKIPS = 4;                     // ¿2? no. of frames that can be skipped in any one animation loop
                                                                // i.e the games state is updated but not rendered
    // Parametros de pantalla y buffering
    private int scr_width, scr_height;                                    
    private BufferStrategy bufferStrategy;
    private boolean fsem = false;
    private GraphicsDevice gd;

    // Elementos del motor
    private InputController input;
    private RenderEngine    render;
    //private AudioEngine     audio = new AudioEngine();
    private ControladorCarrera race_ctrl = null;

    // Parametros de tiempo
    private long period;
    private long gameStartTime;

    //Config del coche e hilo principal
    private Settings conf;
    private Thread play;

    /**
     * Inicializa todos datos de la aplicación y establece una tasa de refresco (titulo de ventana, tamaño, etc...)
     *
     * @param peri Periodo de actualización
     */
    public DrazingApp(long peri)
    {
        // Menús iniciales
        conf = initMenus(version);
        fsem = conf.getFSEM();
        if (fsem)
        {
            this.period = peri;
            initFullScreen();
        }
        else
        {
            Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
            scr_width = dim.width;
            scr_height = dim.height;

            addWindowListener(new WindowAdapter() {
            @Override
                public void windowClosing(WindowEvent e) {
                    System.exit(0);
                }
            });
            

            setPreferredSize(new Dimension(scr_width, scr_height));
            setFocusable(true);
            setResizable(false);
            setVisible(true);
        }   

        // Creación del controlador
        race_ctrl = new ControladorCarrera(1,1,scr_width, scr_height, conf);

        // Entrada
        input = new InputController();
        addKeyListener(input);

        // Creación del render engine
        render = new RenderEngine(race_ctrl, bufferStrategy);
        
        // Inicio de la animacion
        play = new Thread (this);
        play.start();
    }

    /**
     * Lanza los menús iniciales y guarda los settings del coche
     *
     * @param args Argumentos de ejecución
     * @throws Exception
     */
    public static void main(String[] args) throws Exception
    {
        long max_time_frame = (long) 1000.0/DEFAULT_FPS;
        DrazingApp drazingApp = new DrazingApp(max_time_frame * 1000000L);
    }
    /**
     * Inicializa los menus
     * @return settings del coche
     */
    public Settings initMenus(String version)
    {
        //audio.playMusic();
        Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
        WellcomeDiag diag = new WellcomeDiag(dim, version);

        Settings setup = new Settings();
        boolean fs = diag.fs;
        
        SettingDiag setupDiag = new SettingDiag(dim);
        setup = setupDiag.setup;
        setup.setFSEM(fs);
        return setup;
    }

    /**
     * Ejecuta el hilo principal del juego con el bucle
     */
    @Override
    public void run()
    {
        long nanoTime, runingTime, sleepTime, iterationStartTime;
        long overSleepTime = 0L;
        int noDelays = 0;
        long excess = 0L;

        gameStartTime = System.currentTimeMillis()* 1000000;

        while(input.isRunning())
        {
            iterationStartTime = System.currentTimeMillis();
            nanoTime = iterationStartTime*1000000;
            runingTime = nanoTime - gameStartTime;
            sleepTime = (period - runingTime) - overSleepTime;

            if (sleepTime > 0)
            {   // Queda tiempo en este ciclo
                try
                {
                    Thread.sleep(sleepTime/1000000);
                }
                catch(InterruptedException ex){}
                overSleepTime = ((System.currentTimeMillis()*1000000) - nanoTime) - sleepTime;
            }
            else
            {    // sleepTime <= 0; El frame está tomando más tiempo del permitido
                excess -= sleepTime;  // almacenamos el exceso
                overSleepTime = 0L;

                if (++noDelays >= NO_DELAYS_PER_YIELD)
                {
                    Thread.yield();   // da a otro hilo la cpu
                    noDelays = 0;
                }
            }
                                                // Si la animación está tomando demasiado tiempo, paint the game state
                                                // without rendering it, to get the updates/sec nearer to
                                                // the required FPS.
            int skips = 0;
            while((excess > period) && (skips < MAX_FRAME_SKIPS))
            {
                excess -= period;
                gameUpdate();    // paint state but don't render
                skips++;
            }
            screenUpdate();
            double elapsed = 1000.0/(System.currentTimeMillis()-iterationStartTime);
            System.out.println(String.valueOf(elapsed)+" fps");
        }
        StatsDiag statsDiag = new StatsDiag(new Dimension(scr_width, scr_height),conf);
        System.exit(0);
   }

    /**
     * Actualiza el estado del juego
     */
    private void gameUpdate()
    {
        race_ctrl.processes(input.getCommand());
    }

    /**
     * Actualiza la pantalla
     */
    private void screenUpdate()
    {
        if (!render.paint())
            input.exit();
    }

    /**
     * Inicializa el modo FullScreenExclusiveMode (FSEM) si es posible
     * @return success
     */
    public boolean initFullScreen()
    {
        boolean success = true;

        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        gd = ge.getDefaultScreenDevice();
        setUndecorated(true);    
        setIgnoreRepaint(true);
              // turn off paint events since doing active rendering
        setResizable(false);
        if (!gd.isFullScreenSupported())
        {
            System.out.println("Full-screen exclusive mode not supported");
            success = false;
            System.exit(0);
        }
        gd.setFullScreenWindow(this); // switch on FSEM
        // we can now adjust the display modes, if we wish
        //showCurrentMode();   // show the current display mode
        // setDisplayMode(800, 600, 8);   // or try 16 bits
        // setDisplayMode(1280, 1024, 32);
        //reportCapabilities();

        scr_width = getBounds().width;
        scr_height = getBounds().height;
        setBufferStrategy();

        return success;
    }

    /**
     * Establece la estrategia de buffer del modo gráfico
     */
    public void setBufferStrategy()
    {
        try
        {
            EventQueue.invokeAndWait(new Runnable() {
                                                        @Override
                                                        public void run()
                                                        {
                                                            createBufferStrategy(NUM_BUFFERS);
                                                        }
                                                    });
        }
        catch (Exception e)
        {
            System.out.println("Error while creating buffer strategy");
            System.exit(0);
        }
        try
        { // sleep to give time for buffer strategy to be done
            Thread.sleep(100); // 0.5 sec
        }
        catch(InterruptedException ex){}
        createBufferStrategy(NUM_BUFFERS);
        bufferStrategy = getBufferStrategy();  // store for later
    }

}

